# -*- coding: utf-8 -*-
"""
    CSS generator.
"""
from hyde.plugin import Plugin
import contextlib, os, itertools, yaml, shutil

# sentinel
_missing = object()


@contextlib.contextmanager
def working_dir(new_dir):
    orig_dir = os.getcwd()
    os.chdir(new_dir)
    try:
        yield
    finally:
        os.chdir(orig_dir)
        

class cached_property(object):
    """A decorator that converts a function into a lazy property.  The
    function wrapped is called the first time to retrieve the result
    and then that calculated result is used the next time you access
    the value.
    """

    def __init__(self, func, name=None, doc=None):
        self.__name__ = name or func.__name__
        self.__module__ = func.__module__
        self.__doc__ = doc or func.__doc__
        self.func = func
 
    def __get__(self, obj, typ=None):
        if obj is None:
            return self
        value = obj.__dict__.get(self.__name__, _missing)
        if value is _missing:
            value = self.func(obj)
            obj.__dict__[self.__name__] = value
        return value


class CssGenerator(Plugin):
    """
        Say 'ROOT' is a root directory of a site project.
        ROOT/layout
        ROOT/content
        Css resources are placed in ROOT/themes that has such structure:
            ROOT/themes/
                theme1/
                    header.css
                    footer.css
                    post/
                        main.css
                        tags.css
                        category.css
                theme2/
                    header.css
                    footer.css
                    post/
                        main.css
                        tags.css
                        category.css
                common.css
                postcommon.css
       Css config is placed in 'ROOT/layout/css_config.yaml' and has such format:
           base.css:
               /common.css
               header.css
               footer.css
           post.css:
               /postcommon.css
               post/main.css
               post/tags.css
               post/category.css
               
        Here '/' means that this filepath starts from 'ROOT/themes/'.
        Filepaths without starting '/' are relative to 'ROOT/themes/theme_name/'
    """
        
    def __init__(self, site):
        super(CssGenerator, self).__init__(site)
        self.root = self.site.config.sitepath.path
        self.css_config = 'layout/css_config.yaml'
        self.themes_dir = 'themes'
        self.dest_dir = 'content/media/css'

    def begin_site(self):
        self._init_destination_dir()
        for th in self.themes:
            self._generate_theme_css(th)
            
    @cached_property
    def themes(self):
        with working_dir(self.root), working_dir(self.themes_dir):
            return [d for d in os.listdir(os.getcwd()) if os.path.isdir(d)]
    
    @cached_property
    def css_map(self):
        """ Parse 'css_config.yaml'.
        Return:
        
        {
            "make":
            {
                "base.css": {
                    "common": [
                        "common.css"
                    ],
                    
                    "theme": [
                        "footer.css",
                        "header.css"
                    ]
                },
                "post.css": {
                    "common": [
                        "postcommon.css"
                    ],
                    
                    "theme": [
                        "post/category.css",
                        "post/main.css",
                        "post/tags.css"
                    ]
                }
            },
            "common": [
                "common.css",
                "postcommon.css"
            ],
            "theme": [
                "footer.css",
                "header.css",
                "post/category.css",
                "post/main.css",
                "post/tags.css"
                
            ]
        }
        """
        with working_dir(self.root):
            css_config = yaml.load(open(self.css_config, 'r'))
            
        css_map = {
            "make": {}
        }
        _all_common = set()
        _all_theme = set()
        
        for target, deps in css_config.iteritems():
            _make_item = {
                "common": [],
                "theme": []
            }
            for d in deps:
                if d[0] == '/':
                    _make_item['common'].append(d[1:])
                else:
                    _make_item['theme'].append(d)
                    
            css_map['make'][target] = _make_item
            _all_common |= set(_make_item['common'])
            _all_theme |= set(_make_item['theme'])
            
        css_map['common'] = [elem for elem in _all_common]
        css_map['common'].sort()
        css_map['theme'] = [elem for elem in _all_theme]
        css_map['theme'].sort()
        return css_map
            
    def _generate_theme_css(self, theme):
        """ Generate css files for a set theme.
        
        """
        output_css = self._generate_theme_css_inmemory(theme)
            
        with working_dir(self.root), working_dir(os.path.join(self.dest_dir, theme)):
            for fname, fcontent in output_css.iteritems():
                open(fname, 'w').write(fcontent)
                
    def _generate_theme_css_inmemory(self, theme):
        """
        Returns:
            {
                "base.css": "
                    .common {}
                    .footer {}
                    .header {}
                ",
                "post.css": "
                    .postcommon {}
                    .post-category {}
                    .post-main {}
                    .post-tags {}
                "
            }
        """
        
        common_css = self._common_css
        theme_css = self._theme_css(theme)
        css_make_config = self.css_map['make']
        output_css = {}
        
        for out, deps in css_make_config.iteritems():
            _common = (common_css[fn] for fn in deps['common'])
            _theme = (theme_css[fn] for fn in deps['theme'])
            _all = itertools.chain(_common, _theme)
            output_css[out] = "\n".join(_all)
        
        return output_css
    
    @cached_property
    def _common_css(self):
        """
        Returns:
            {
                "common.css": <file content>,
                "postcommon.css": <file content>
            }
        """
        with working_dir(self.root), working_dir(self.themes_dir):
            out = {}
            for fn in self.css_map["common"]:
                out[fn] = open(fn, 'r').read()
            return out
    
    def _theme_css(self, theme):
        """
        Returns:
            {
                "header.css": <file content>,
                "footer.css": <file content>,
                "post/main.css": <file content>,
                "post/tags.css": <file content>,
                "post/category.css": <file content>
            }
        """
        with working_dir(self.root), working_dir(os.path.join(self.themes_dir, theme)):
            out = {}
            for fn in self.css_map["theme"]:
                out[fn] = open(fn, 'r').read()
            return out
    
    def _init_destination_dir(self):
        """ Clears destination directory. Creates per theme directories.
        E.g.:
            ROOT/content/media/css/
                /theme1
                /theme2
        """
        # clear
        with working_dir(self.root), working_dir(self.dest_dir):
            
            for item in os.listdir(os.getcwd()):
                if os.path.isdir(item):
                    shutil.rmtree(item)
                else:
                    os.unlink(item)
            
            for th in self.themes:
                os.mkdir(th)